#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#include "r_socket_ipv4_tcp_server.h"
#include "r_socket_obj_manager.h"

#include "utilities/string_operator.h"
#include "utilities/r_logger.h"

namespace eniac
{

r_socket_proxy_ret_t r_socket_ipv4_tcp_server::set_socket_property(const r_socket::ptr socket_obj, r_socket_property_type type, void* property, size_t property_size)
{
    if (nullptr == socket_obj.get())
    {
        return r_socket_proxy_ret_t::NULL_PTR;
    }

    std::lock_guard<std::mutex> lg{socket_obj->m_mutex};

    if (type == r_socket_property_type::SOCKET_PROPERTY_OWN_ADDRESS)
    {
        return set_socket_own_address(socket_obj, property, property_size);
    }

    if (type == r_socket_property_type::SOCKET_PROPERTY_OWN_PORT_NUMBER)
    {
        return set_socket_own_port(socket_obj, property, property_size);
    }

    if (type == r_socket_property_type::SOCKET_PROPERTY_MAX_ACCEPT_NUMBER)
    {
        return set_socket_max_accept_number(socket_obj, property, property_size);
    }

    return r_socket_proxy_ret_t::INVALID_PROPERTY_TYPE_FOR_THE_SOCKET;
}

r_socket_proxy_ret_t r_socket_ipv4_tcp_server::set_socket_own_address(const r_socket::ptr socket_obj, void* property, size_t property_size)
{
    if ( property_size < strlen("0.0.0.0") ||
         property_size > strlen("000.000.000.000") )
    {
        return r_socket_proxy_ret_t::INVALID_PROPERTY_LENGTH;
    }

    std::string ip_str = reinterpret_cast<char*>(property);

    if (false == is_valid_ipv4_ip(ip_str) )
    {
        return r_socket_proxy_ret_t::INVALID_PROPERTY_VALUE;
    }

    socket_obj->m_own_address = ip_str;

    return r_socket_proxy_ret_t::OK;
}

r_socket_proxy_ret_t r_socket_ipv4_tcp_server::set_socket_own_port(const r_socket::ptr socket_obj, void* property, size_t property_size)
{
    if (property_size != sizeof(socket_obj->m_own_port))
    {
        return r_socket_proxy_ret_t::INVALID_PROPERTY_LENGTH;
    }

    if (INVALID_SOCKET_PORT == *reinterpret_cast<uint16_t*>(property))
    {
        return r_socket_proxy_ret_t::INVALID_PROPERTY_VALUE;
    }

    socket_obj->m_own_port = *reinterpret_cast<uint16_t*>(property);

    return r_socket_proxy_ret_t::OK;
}

r_socket_proxy_ret_t r_socket_ipv4_tcp_server::set_socket_max_accept_number(const r_socket::ptr socket_obj, void* property, size_t property_size)
{
    if (property_size != sizeof(socket_obj->m_max_listen_number))
    {
        return r_socket_proxy_ret_t::INVALID_PROPERTY_LENGTH;
    }

    if (0 >= *reinterpret_cast<int32_t*>(property))
    {
        return r_socket_proxy_ret_t::INVALID_PROPERTY_VALUE;
    }

    socket_obj->m_max_listen_number = *reinterpret_cast<int32_t*>(property);
    
    return r_socket_proxy_ret_t::OK;
}

r_socket_proxy_ret_t r_socket_ipv4_tcp_server::start_socket(const r_socket::ptr socket_obj)
{
    if (nullptr == socket_obj.get())
    {
        return r_socket_proxy_ret_t::NULL_PTR;
    }

    std::lock_guard<std::mutex> lg{socket_obj->m_mutex};

    if (socket_obj->m_own_address.empty())
    {
        return r_socket_proxy_ret_t::OWN_ADDRESS_HAS_NOT_BEEN_SET_YET;
    }

    if (socket_obj->m_own_port == INVALID_SOCKET_PORT)
    {
        return r_socket_proxy_ret_t::OWN_PORT_HAS_NOT_BEEN_SET_YET;
    }

    if (socket_obj->m_max_listen_number == -1)
    {
        return r_socket_proxy_ret_t::MAX_LISTEN_NUMBER_HAS_NOT_BEEN_SET_YET;
    }

    if (socket_obj->m_socket_fd != INVALID_SOCKET_FD)
    {
        return r_socket_proxy_ret_t::SOCKET_FD_HAS_BEEN_SET;
    }

    if ((socket_obj->m_socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        return r_socket_proxy_ret_t::TO_CREATE_SOCKET_FD_IS_FAILED;
    }

    LOG_DBG() << "IP is " << socket_obj->m_own_address;
    LOG_DBG() << "Port is " << socket_obj->m_own_port;

    struct sockaddr_in address;
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = ipv4_to_net_int(socket_obj->m_own_address);
    address.sin_port = htons(socket_obj->m_own_port);
    int bind_ret = bind(socket_obj->m_socket_fd, (struct sockaddr*)&address, sizeof(address));
    if (bind_ret < 0)
    {
        LOG_DBG() << "Bind Ret is " << bind_ret << " errno " <<  errno <<  "  strerror(errno) " << strerror(errno);
        close(socket_obj->m_socket_fd);
        socket_obj->m_socket_fd = INVALID_SOCKET_FD;
        return r_socket_proxy_ret_t::TO_BIND_ADDRESS_TO_FD_IS_FAILED;
    }

    if (listen(socket_obj->m_socket_fd, socket_obj->m_max_listen_number) < 0)
    {
        close(socket_obj->m_socket_fd);
        socket_obj->m_socket_fd = INVALID_SOCKET_FD;
        return r_socket_proxy_ret_t::TO_START_LISTEN_FD_IS_FAILED;
    }

    socket_obj->m_status = r_socket_status_t::R_SOCKET_STATUS_STARTED;
    r_socket_obj_manager::get_instance()->update_socket_obj(socket_obj);
    return r_socket_proxy_ret_t::OK;
}

r_socket_proxy_ret_t r_socket_ipv4_tcp_server::accept_socket(const r_socket::ptr socket_obj_self)
{
    if (nullptr == socket_obj_self.get())
    {
        return r_socket_proxy_ret_t::NULL_PTR;
    }

    if (socket_obj_self->m_socket_fd == INVALID_SOCKET_FD)
    {
        return r_socket_proxy_ret_t::INVALID_SOCKET_FD;
    }

    std::lock_guard<std::mutex> lg{socket_obj_self->m_mutex};

    struct sockaddr_in address;
    int addrlen = sizeof(address);
    int32_t accepted_socket_fd = accept(socket_obj_self->m_socket_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
    if (accepted_socket_fd < 0)
    {
        return r_socket_proxy_ret_t::TO_ACCEPT_SOCKET_IS_FAILED;
    }

    r_socket::ptr accepted_socket_obj = r_socket_obj_manager::get_instance()->create_socket_obj(r_socket_type::SOCKET_TYPE_IPV4_TCP_PARTNER);
    accepted_socket_obj->m_socket_type = r_socket_type::SOCKET_TYPE_IPV4_TCP_PARTNER;
    accepted_socket_obj->m_socket_fd = accepted_socket_fd;
    accepted_socket_obj->m_own_address.resize(INET_ADDRSTRLEN);
    inet_ntop(AF_INET, &(address.sin_addr), accepted_socket_obj->m_own_address.data(), INET_ADDRSTRLEN);
    accepted_socket_obj->m_own_port = ntohs(address.sin_port);
    accepted_socket_obj->m_status = r_socket_status_t::R_SOCKET_STATUS_STARTED;
    accepted_socket_obj->m_host_id = socket_obj_self->m_socket_id;
    r_socket_obj_manager::get_instance()->update_socket_obj(accepted_socket_obj);
    socket_obj_self->m_parnters.insert(accepted_socket_obj->m_socket_id);
    r_socket_obj_manager::get_instance()->update_socket_obj(socket_obj_self);
    LOG_INF() << "Accepted Socket IP is " <<  accepted_socket_obj->m_own_address << " Port is " << accepted_socket_obj->m_own_port;

    return r_socket_proxy_ret_t::OK;
}

void r_socket_ipv4_tcp_server::close_socket(const r_socket::ptr socket_obj)
{
    if (nullptr == socket_obj.get())
    {
        return;
    }

    if (socket_obj->m_socket_fd == INVALID_SOCKET_FD)
    {
        return;
    }

    std::lock_guard<std::mutex> lg{socket_obj->m_mutex};
    for (const auto& partner_id : socket_obj->m_parnters)
    {
        r_socket::ptr partner =  r_socket_obj_manager::get_instance()->get_socket_obj_by_id(partner_id);
        if (nullptr == partner.get())
        {
            continue;
        }
        if (INVALID_SOCKET_FD != partner->m_socket_fd)
        {
            close(partner->m_socket_fd);
            partner->m_socket_fd = INVALID_SOCKET_FD;
        }
        r_socket_obj_manager::get_instance()->remove_socket_obj(partner->m_socket_id);
    }

    close(socket_obj->m_socket_fd);
    socket_obj->m_socket_fd = INVALID_SOCKET_FD;
    socket_obj->m_status = r_socket_status_t::R_SOCKET_STATUS_STOPPING;
    r_socket_obj_manager::get_instance()->update_socket_obj(socket_obj);
}

}